#include "Strategies.h"
#include "Helper.h"
#include <set>
#include <sstream>
#include <fstream>
#include <numeric>
#include <thread>

std::atomic_uint32_t StrategyTask::taskAtomicIdx;
std::vector<std::shared_ptr<BaseStrategy>> StrategyTask::strategies;

std::shared_ptr<BaseStrategy> StrategyTask::GetNextTask(uint32_t& idx)
{
    idx = taskAtomicIdx.fetch_add(1);
    return idx >= strategies.size() ? nullptr : strategies[idx];
}

StrategyTask::StrategyTask()
{}

void StrategyTask::operator()()
{
    uint32_t idx = 0;
    auto strategy = GetNextTask(idx);
    while (strategy.get())
    {
        uint32_t subjectLength = strategy->GetParam().subjectLength;
        for (uint32_t i = 0; i < subjectLength; i++)
        {
            strategy->PreUpdateStat();
            strategy->Do(i);
            strategy->PostUpdateStat(i);
            strategy->UpdateResult();
        }
        strategy = GetNextTask(idx);
    }
}

StrategyTester::StrategyTester(std::vector<std::shared_ptr<BaseStrategy>> in_strategies, uint32_t in_length):
    strategies(in_strategies),
    length(in_length)
{}

StrategyTester::~StrategyTester()
{
}

void StrategyTester::InitAllTasks()
{
    uint32_t totalTaskSize = strategies.size();
    uint32_t coreNum = UtilsHelper::GetProcessorCount();
    tasks.resize(coreNum, {});
    StrategyTask::taskAtomicIdx.store(0);
    StrategyTask::strategies = strategies;
}

void StrategyTester::Reset()
{
    threads.clear();
    tasks.clear();
}

void StrategyTester::Execute()
{
    TimeCounter timeCounter;

    for (auto& it : tasks)
    {
        threads.push_back(std::thread(it));
    }

    uint32_t idx = 0;
    uint32_t count = StrategyTask::strategies.size();
    do
    {
        idx = std::min<uint32_t>(StrategyTask::taskAtomicIdx.load(), count);
        std::cout << "Processing " << idx << "/" << count << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    } while (idx < count);

    for (auto& it : threads)
    {
        it.join();
    }
    std::cout << "Processed finished!" << std::endl;
    StrategyTask::taskAtomicIdx.store(0);
}

void StrategyTester::ReserveNeededStrategies()
{
    std::set<std::shared_ptr<BaseStrategy>> strategiesNeedReserve;
    uint32_t testIdx = 0;
    uint32_t resultIdx = 0;
    double resultMaxYieldRate = std::numeric_limits<double>::min();
    for (const auto& it : strategies)
    {
        auto& result = it->GetResult();
        auto finalYieldRate = result.recordInfo.back().globalYieldRate;
        double maxDrawDown = result.maxDrawdown;
        if (finalYieldRate > resultMaxYieldRate /*&& maxDrawDown < 0.1*/)
        {
            resultMaxYieldRate = finalYieldRate;
            resultIdx = testIdx;
        }
        testIdx++;
    }

    auto& strategy = strategies[resultIdx];
    strategy->Reset();
    strategy->SetRecord(true);
    strategiesNeedReserve.emplace(strategy);
    
    strategies.clear();
    tasks.clear();
    threads.clear();
    for (auto& it : strategiesNeedReserve)
    {
        strategies.push_back(it);
    }
}

void StrategyTester::DumpAllFinalStrategies()
{
    for (uint32_t idx = 0; idx < strategies.size(); idx++)
    {
        auto& it = strategies[idx];
        if (it)
        {
            const auto& param = it->GetParam();
            const auto& subjects = param.subjects;
            it->DumpParameters();

            auto& result = it->GetResult();
            auto& lastInfo = result.recordInfo.back();
            double principal = lastInfo.principal;
            double finalAsset = 0.0;
            for (size_t i = 0; i < lastInfo.holdCount.size(); i++)
            {
                finalAsset += lastInfo.holdCount[i] * subjects[i]->closedPrices.back();
            }
            double finalCash = lastInfo.cash + finalAsset;
            double yieldRate = lastInfo.globalYieldRate;
            std::cout << "GlobalYieldRate " << yieldRate << " Principal " << principal << " FinalCash " << finalCash << std::endl;
            result.SaveRecordsToCSV("../Results/Result_" + std::to_string(idx) + ".csv", subjects);
        }
    }
}

std::string StrategyResult::RecordInfoToCSVString(const RecordInfo& recordInfo, const std::vector<std::shared_ptr<Subject>>& subjects)
{
    std::ostringstream oss;
    oss.setf(std::ios::fixed);
    oss.precision(3);
    oss << recordInfo.idx << ','
        << recordInfo.cash << ',';
    uint32_t idx = recordInfo.idx;
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->closedPrices[idx]; oss << ',';}
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->MA5[idx]; oss << ',';}
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->MA10[idx]; oss << ',';}
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->MA20[idx]; oss << ',';}
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->MA60[idx]; oss << ',';}
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->MA120[idx]; oss << ',';}
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->MACD[idx]; oss << ',';}
    oss << recordInfo.totalAsset << ',';
    for (size_t i = 0; i < recordInfo.meanPrice.size(); i++) {oss << recordInfo.meanPrice[i]; oss << ',';}
    for (size_t i = 0; i < recordInfo.holdCount.size(); i++) {oss << recordInfo.holdCount[i]; oss << ',';}
    oss << recordInfo.principal << ',';
    for (size_t i = 0; i < recordInfo.localYiedRate.size(); i++)   {oss << recordInfo.localYiedRate[i]; oss << ',';}
    oss << recordInfo.globalYieldRate << ',';
    for (size_t i = 0; i < subjects.size(); i++) {oss << subjects[i]->YieldRate[idx]; oss << ',';}
    return oss.str();
}

void StrategyResult::SaveRecordsToCSV(const std::string& filename, const std::vector<std::shared_ptr<Subject>>& subjects)
{
    std::ofstream file(filename);

    if (!file.is_open()) {
        std::cerr << "Can not open file: " << filename << std::endl;
        return;
    }


    // Write CSV header.
    file << "Index, Cash,";
    for (size_t i = 0; i < subjectCount; i++) { file << " Price_" + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " MA5_" + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " MA10_" + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " MA20_" + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " MA60_" + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " MA120_" + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " MACD_" + std::to_string(i) + ","; }
    file << " Total Asset,";
    for (size_t i = 0; i < subjectCount; i++) { file << " Mean Price " + std::to_string(i) + ","; }
    for (size_t i = 0; i < subjectCount; i++) { file << " Hold Count " + std::to_string(i) + ","; }
    file << " Principal,";
    for (size_t i = 0; i < subjectCount; i++) { file << " Local Yied Rate " + std::to_string(i) + ","; }
    file << " Global Yield Rate,";
    for (size_t i = 0; i < subjectCount; i++) { file << " Origin Yield Rate " + std::to_string(i) + ","; }
    file << '\n';

    // Write every record info.
    for (const auto& record : recordInfo)
    {
        file << RecordInfoToCSVString(record, subjects) << "\n";
    }

    file.close();
    std::cout << "Data has saved successfully:  " << filename << std::endl;
}

void BaseStrategy::DumpParameters()
{
    std::cout << "Start Cash " << param.startCash << " Subject Count " << param.subjectCount << " Subject Length " << param.subjectLength << std::endl;
}

void BaseStrategy::BuyInPrice(double money, double p, uint32_t idx)
{
    money = std::min(money, stat.cash);
    uint32_t count = std::floor(money / p);
    double cost = count * p;
    stat.costCash[idx] += cost;
    stat.holdCount[idx] += count;
    stat.cash -= cost;
}

void BaseStrategy::SaleInPrice(double money, double p, uint32_t idx)
{
    uint32_t count = std::floor(money / p);
    double cost = count * p;
    stat.costCash[idx] -= cost;
    stat.holdCount[idx] -= count;
    stat.cash += cost;
}

void BaseStrategy::SaleInCount(uint32_t count, double p, uint32_t idx)
{
    double cost = count * p;
    stat.costCash[idx] -= cost;
    stat.holdCount[idx] -= count;
    stat.cash += cost;
}

double BaseStrategy::UpdatePrinciple()
{
    return stat.principle;
}

double BaseStrategy::UpdateTotalAsset()
{
    double totalAsset = stat.cash;
    for (size_t i = 0; i < param.subjectCount; i++)
    {
        totalAsset += stat.holdCount[i] * param.subjects[i]->closedPrices[stat.lastIndex];
    }
    return totalAsset;
}

void BaseStrategy::PreUpdateStat()
{
}

void BaseStrategy::PostUpdateStat(uint32_t idx)
{
    stat.principle = UpdatePrinciple();
    stat.totalAsset = UpdateTotalAsset();
    stat.globalYieldRate = (stat.totalAsset - stat.principle) / stat.principle;
    result.maxDrawdown = std::max(result.maxDrawdown, -stat.globalYieldRate);
    result.maxYieldRate = std::max(result.maxYieldRate, stat.globalYieldRate);

    for (size_t i = 0; i < param.subjectCount; i++)
    {
        stat.meanPrice[i] = stat.holdCount[i] == 0 ? 0 : stat.costCash[i] / stat.holdCount[i];
        double lastPrice = param.subjects[i]->closedPrices[stat.lastIndex];
        stat.localYiedRate[i] = stat.holdCount[i] == 0 ? 0 : (lastPrice - stat.meanPrice[i]) / stat.meanPrice[i];
        stat.startPrice[i] = stat.lastIndex == 0 ? lastPrice : stat.startPrice[i];
    }
    stat.lastIndex = idx;
}

void BaseStrategy::UpdateResult()
{
    RecordInfo info{};
    info.cash = stat.cash;
    info.totalAsset = stat.totalAsset;
    info.globalYieldRate = stat.globalYieldRate;
    info.holdCount = stat.holdCount;
    info.idx = stat.lastIndex;
    info.localYiedRate = stat.localYiedRate;
    info.meanPrice = stat.meanPrice;
    info.principal = stat.principle;

    if (bEnableRecord)
    {
        result.recordInfo.push_back(info);
    }
    else
    {
        result.recordInfo[0] = info;
    }
}

//void GREEDStrategy::Init(double in_startCash, double in_everyDayCash, double in_baseExtraCash, double in_stopProfitThreadHold, double in_saleRate)
//{
//    startCash = in_startCash;
//    cash = startCash;
//    everyDayCash = in_everyDayCash;
//    baseExtraCash = 100.0 * in_baseExtraCash;
//    stopProfitThreadHold = in_stopProfitThreadHold;
//    saleRate = in_saleRate;
//}

#if 0

void GREEDStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();
    maxPrice = std::numeric_limits<double>::min();
}

void GREEDStrategy::DumpParameters()
{
    std::cout << "BaseExtraCash " << 0.01 * baseExtraCash << " StopProfitThreadHold " << stopProfitThreadHold << " SaleRate " << saleRate << std::endl;
}

void GREEDStrategy::Do(uint32_t idx)
{
    stat.cash += everyDayCash;
    maxPrice = std::max(maxPrice, p[0]);
    double decRate = (maxPrice - p[0]) / maxPrice;

    double buyMoney = decRate * baseExtraCash;
    buyMoney = std::min(buyMoney, stat.cash);
    buyMoney = std::max(buyMoney, 0.0);


    double meanPrice = stat.holdCount[0] == 0 ? 0 : stat.costCash[0] / stat.holdCount[0];
    double localYiedRate = stat.holdCount[0] == 0 ? 0 : (stat.lastPrice[0] - meanPrice) / meanPrice;

    if (localYiedRate > stopProfitThreadHold)
    {
        stat.costCash[0] = 0;
        stat.cash += stat.holdCount[0] * p[0];
        stat.holdCount[0] = 0;
    }
    else
    {
        BuyInPrice(buyMoney, p[0], 0);
    }

    //cash *= 1.00005;

    UpdateResult();
}

void GREEDStrategy::UpdateResult()
{
    BaseStrategy::UpdateResult();
    double principle = startCash + everyDayCash * lastTime;
    double totalAsset = 0.0;
    for (size_t i = 0; i < holdCount.size(); i++)
    {
        totalAsset += holdCount[i] * lastPrice[i];
    }
    double totalCash = cash + totalAsset;
    double globalYieldRate = (totalCash - principle) / principle;

    std::vector<double> meanPrice;
    meanPrice.resize(, 0.0)
    double meanPrice = holdCount == 0 ? 0 : costCash / holdCount;
    double localYiedRate = holdCount == 0 ? 0 : (lastPrice - meanPrice) / meanPrice;

    result.maxDrawdown = std::max(result.maxDrawdown, -globalYieldRate);
    result.maxYieldRate = std::max(result.maxYieldRate, globalYieldRate);

    startPrice = lastTime == 1 ? lastPrice : startPrice;
    double originRate = (lastPrice - startPrice) / startPrice;

    double macd = UpdateMACD(lastPrice);

    RecordInfo info{};
    info.cash = cash;
    info.macd = macd;
    info.totalAsset = totalCash;
    info.globalYieldRate = globalYieldRate;
    info.holdCount = holdCount;
    info.idx = lastTime;
    info.localYiedRate = localYiedRate;
    info.meanPrice = meanPrice;
    info.price = lastPrice;
    info.principal = principle;
    info.originYieldRate = originRate;

    if (bEnableRecord)
    {
        result.recordInfo.push_back(info);
    }
    else
    {
        result.recordInfo[0] = info;
    }
}

void MACDStrategy::Init(double in_startCash, double in_everyDayCash)
{
    startCash = in_startCash;
    everyDayCash = in_everyDayCash;
}

void MACDStrategy::Reset()
{
    BaseStrategy::Reset();
    maxDrawdown = std::numeric_limits<double>::min();
    lastMACDNegative = true;
    macd = 0.0;
}

void MACDStrategy::DumpParameters()
{
    std::cout << "MACD Strategy."<< std::endl;
}

void MACDStrategy::Do(uint32_t idx)
{
    lastTime++;
    lastPrice = p;

    cash += everyDayCash;
    macd = UpdateMACD(lastPrice);
    if (lastMACDNegative && macd > 0.0)
    {
        BuyInPrice(cash, p);
    }
    else if (!lastMACDNegative && macd < 0.0)
    {
        costCash = 0;
        cash += holdCount * p;
        holdCount = 0;
    }
    lastMACDNegative = macd < 0.0;
    UpdateResult();
}

void MACDStrategy::UpdateResult()
{
    double principle = startCash + everyDayCash * lastTime;
    double totalCash = cash + holdCount * lastPrice;
    double globalYieldRate = (totalCash - principle) / principle;

    double meanPrice = holdCount == 0 ? 0 : costCash / holdCount;
    double localYiedRate = holdCount == 0 ? 0 : (lastPrice - meanPrice) / meanPrice;

    result.maxDrawdown = std::max(result.maxDrawdown, -globalYieldRate);
    result.maxYieldRate = std::max(result.maxYieldRate, globalYieldRate);

    startPrice = lastTime == 1 ? lastPrice : startPrice;
    double originRate = (lastPrice - startPrice) / startPrice;

    RecordInfo info{};
    info.cash = cash;
    info.macd = macd;
    info.totalAsset = totalCash;
    info.globalYieldRate = globalYieldRate;
    info.holdCount = holdCount;
    info.idx = lastTime;
    info.localYiedRate = localYiedRate;
    info.meanPrice = meanPrice;
    info.price = lastPrice;
    info.principal = principle;
    info.originYieldRate = originRate;

    if (bEnableRecord)
    {
        result.recordInfo.push_back(info);
    }
    else
    {
        result.recordInfo[0] = info;
    }
}

void NINEStrategy::Init(double in_startCash, double in_everyDayCash)
{
    startCash = in_startCash;
    everyDayCash = in_everyDayCash;
}

void NINEStrategy::Reset()
{
    BaseStrategy::Reset();
    nineUp = 0;
    nineDown = 0;
    maxDrawdown = std::numeric_limits<double>::min();
}

void NINEStrategy::DumpParameters()
{
    std::cout << "NINE" << std::endl;
}

void NINEStrategy::Do(uint32_t idx)
{
    lastTime++;
    lastPrice = p;

    cash += everyDayCash;
    if (fourDayPrices.size() == 4)
    {
        double fourPrice = fourDayPrices.front();
        if (p > fourPrice)
        {
            nineDown = 0;
            nineUp++;
        }
        else
        {
            nineUp = 0;
            nineDown++;
        }

        if (nineDown >= 9)
        {
            BuyInPrice(cash, p);
            nineDown = 0;
        }
        else if(nineUp >= 9)
        {
            nineUp = 0;
            //double meanPrice = holdCount == 0 ? 0 : costCash / holdCount;
            //double localYiedRate = holdCount == 0 ? 0 : (lastPrice - meanPrice) / meanPrice;
            //if (localYiedRate > 0.0)
            //{
            //    costCash = 0;
            //    cash += holdCount * p;
            //    holdCount = 0;
            //    nineUp = 0;
            //}
        }
        fourDayPrices.pop();
    }
    fourDayPrices.push(p);
    UpdateResult();
}

void NINEStrategy::UpdateResult()
{
    double principle = startCash + everyDayCash * lastTime;
    double totalCash = cash + holdCount * lastPrice;
    double globalYieldRate = (totalCash - principle) / principle;

    double meanPrice = holdCount == 0 ? 0 : costCash / holdCount;
    double localYiedRate = holdCount == 0 ? 0 : (lastPrice - meanPrice) / meanPrice;

    result.maxDrawdown = std::max(result.maxDrawdown, -globalYieldRate);
    result.maxYieldRate = std::max(result.maxYieldRate, globalYieldRate);

    startPrice = lastTime == 1 ? lastPrice : startPrice;
    double originRate = (lastPrice - startPrice) / startPrice;

    double macd = UpdateMACD(lastPrice);

    RecordInfo info{};
    info.cash = cash;
    info.macd = macd;
    info.totalAsset = totalCash;
    info.globalYieldRate = globalYieldRate;
    info.holdCount = holdCount;
    info.idx = lastTime;
    info.localYiedRate = localYiedRate;
    info.meanPrice = meanPrice;
    info.price = lastPrice;
    info.principal = principle;
    info.originYieldRate = originRate;

    if (bEnableRecord)
    {
        result.recordInfo.push_back(info);
    }
    else
    {
        result.recordInfo[0] = info;
    }
}

void DCAStrategy::Init(double in_startCash, double in_everyDayCash, double in_baseExtraCash, double in_growthRate)
{
    startCash = in_startCash;
    cash = startCash;
    everyDayCash = in_everyDayCash;
    baseExtraCash = in_baseExtraCash;
    growthRate = in_growthRate;
}

void DCAStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();
}

void DCAStrategy::DumpParameters()
{
    std::cout << "BaseExtraCash " << baseExtraCash << " growthRate " << growthRate << std::endl;
}

void DCAStrategy::Do(uint32_t idx)
{
    lastTime++;
    lastPrice = p;

    cash += everyDayCash;

    double propertyShouldBe = baseExtraCash * (1 - std::pow(growthRate, lastTime)) / (1 - growthRate);
    double currentProperty = holdCount * p;
    double diff = propertyShouldBe - currentProperty;

    if (diff > 0.0 && diff > lastDiff)
    {
        double buyPrice = std::min(cash, diff);
        BuyInPrice(buyPrice, p);
    }
    lastDiff = diff;
    UpdateResult();
}

void DCAStrategy::UpdateResult()
{
    double principle = startCash + everyDayCash * lastTime;
    double totalCash = cash + holdCount * lastPrice;
    double globalYieldRate = (totalCash - principle) / principle;

    double meanPrice = holdCount == 0 ? 0 : costCash / holdCount;
    double localYiedRate = holdCount == 0 ? 0 : (lastPrice - meanPrice) / meanPrice;

    result.maxDrawdown = std::max(result.maxDrawdown, -globalYieldRate);
    result.maxYieldRate = std::max(result.maxYieldRate, globalYieldRate);

    startPrice = lastTime == 1 ? lastPrice : startPrice;
    double originRate = (lastPrice - startPrice) / startPrice;

    double macd = UpdateMACD(lastPrice);

    RecordInfo info{};
    info.cash = cash;
    info.macd = macd;
    info.totalAsset = totalCash;
    info.globalYieldRate = globalYieldRate;
    info.holdCount = holdCount;
    info.idx = lastTime;
    info.localYiedRate = localYiedRate;
    info.meanPrice = meanPrice;
    info.price = lastPrice;
    info.principal = principle;
    info.originYieldRate = originRate;

    if (bEnableRecord)
    {
        result.recordInfo.push_back(info);
    }
    else
    {
        result.recordInfo[0] = info;
    }
}
#endif // 0

GOLDIXICStrategy::GOLDIXICStrategy(BaseStrategy::Param in_baseParam, GOLDIXICStrategy::Param in_param) : BaseStrategy(in_baseParam), param(in_param){}

void GOLDIXICStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();
}

void GOLDIXICStrategy::DumpParameters()
{
    std::cout << "everyDayCash " << param.everyDayCash <<
    " gold_rate " << param.gold_rate <<
    " ixic_rate " << param.ixic_rate <<
    " refixRate_gold_to_ixic " << param.refixRate_gold_to_ixic <<
    " refixRate_ixic_to_gold " << param.refixRate_ixic_to_gold <<
    std::endl;
}

void GOLDIXICStrategy::Do(uint32_t idx)
{
    BaseStrategy::stat.cash += param.everyDayCash;
    BaseStrategy::stat.principle += param.everyDayCash;

    uint32_t perioid = 21;
    double deltaCash = BaseStrategy::param.startCash / (BaseStrategy::param.subjectLength / perioid);
    
    uint32_t goldIdx = 0;
    double goldPrice = BaseStrategy::param.subjects[goldIdx]->closedPrices[idx];

    uint32_t ixicIdx = 1;
    double ixicPrice = BaseStrategy::param.subjects[ixicIdx]->closedPrices[idx];

    if (BaseStrategy::stat.lastIndex % perioid == 0)
    {
        double buyPrice = deltaCash + param.everyDayCash * perioid;
        buyPrice = std::min(BaseStrategy::stat.cash, buyPrice);

        double goldBuyPrice = buyPrice * param.gold_rate;
        goldBuyPrice = std::min(BaseStrategy::stat.cash, goldBuyPrice);


        BuyInPrice(goldBuyPrice, goldPrice, goldIdx);

        double ixicBuyPrice = buyPrice * param.ixic_rate;
        ixicBuyPrice = std::min(BaseStrategy::stat.cash, ixicBuyPrice);

        BuyInPrice(ixicBuyPrice, ixicPrice, ixicIdx);
    }

    uint32_t goldHold = BaseStrategy::stat.holdCount[goldIdx];
    double goldAsset = goldHold * goldPrice;

    uint32_t ixicHold = BaseStrategy::stat.holdCount[ixicIdx];
    double ixicAsset = ixicHold * ixicPrice;
    
    double asset = goldAsset + ixicAsset;

    stat.gold_asset_rate = goldAsset / asset;
    stat.ixic_asset_rate = ixicAsset / asset;

    if (stat.gold_asset_rate > param.refixRate_gold_to_ixic)
    {
        double k = param.gold_rate / param.ixic_rate;
        double refixcash = (goldAsset - k * ixicAsset) / (k + 1.0);
        SaleInPrice(refixcash, goldPrice, goldIdx);
        BuyInPrice(refixcash, ixicPrice, ixicIdx);
    }
    else if(stat.ixic_asset_rate > param.refixRate_ixic_to_gold)
    {
        double k =  param.ixic_rate / param.gold_rate;
        double refixcash = (ixicAsset - k * goldAsset) / (k + 1.0);
        SaleInPrice(refixcash, ixicPrice, ixicIdx);
        BuyInPrice(refixcash, goldPrice, goldIdx);
    }
}

MAGREEDStrategy::MAGREEDStrategy(BaseStrategy::Param in_baseParam, MAGREEDStrategy::Param in_param) : BaseStrategy(in_baseParam), param(in_param) {}

void MAGREEDStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();
}

void MAGREEDStrategy::DumpParameters()
{
    std::cout << "everyDayCash " << param.everyDayCash << " baseExtraCash " << param.baseExtraCash << " minInvestThreadHold " << param.minInvestThreadHold << std::endl;
}

void MAGREEDStrategy::Do(uint32_t idx)
{
    BaseStrategy::stat.cash += param.everyDayCash;
    BaseStrategy::stat.principle += param.everyDayCash;

    double price = BaseStrategy::param.subjects[0]->closedPrices[idx];

    auto& subjectRef = BaseStrategy::param.subjects[0];

    double drawDown5 =   (subjectRef->MA5[idx]   - price) / subjectRef->MA5[idx];
    double drawDown10 =  (subjectRef->MA10[idx]  - price) / subjectRef->MA10[idx];
    double drawDown20 =  (subjectRef->MA20[idx]  - price) / subjectRef->MA20[idx];
    double drawDown60 =  (subjectRef->MA60[idx]  - price) / subjectRef->MA60[idx];
    double drawDown120 = (subjectRef->MA120[idx] - price) / subjectRef->MA120[idx];
    double drawDown250 = (subjectRef->MA250[idx] - price) / subjectRef->MA250[idx];
    double maxDrawdown = std::max({ 0.0, drawDown5, drawDown10, drawDown20, drawDown60, drawDown120, drawDown250 });
    double rate = maxDrawdown;
    double buyMoney = rate * 100.0 * param.baseExtraCash;
    if (buyMoney >= param.minInvestThreadHold)
    {
        BuyInPrice(buyMoney, price, 0);
    }
}

RemainStrategy::RemainStrategy(BaseStrategy::Param in_baseParam, RemainStrategy::Param in_param) : BaseStrategy(in_baseParam), param(in_param) {}

void RemainStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();
}

void RemainStrategy::DumpParameters()
{
    std::cout << "everyDayCash " << param.everyDayCash << " remainDays " << param.remainDays << std::endl;
}

void RemainStrategy::Do(uint32_t idx)
{
    BaseStrategy::stat.cash += param.everyDayCash;
    BaseStrategy::stat.principle += param.everyDayCash;

    const auto& prices = BaseStrategy::param.subjects[0]->closedPrices;
    double price = prices[idx];

    if (idx > 0 && prices[idx] < prices[idx - 1])
    {
        double deltaMoney = BaseStrategy::stat.cash / param.remainDays;
        BuyInPrice(deltaMoney, price, 0);
    }
}

WaterChickenStrategy::WaterChickenStrategy(BaseStrategy::Param in_baseParam, WaterChickenStrategy::Param in_param) : BaseStrategy(in_baseParam), param(in_param) {}

void WaterChickenStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();

    bStartWaterChicken = false;
}

void WaterChickenStrategy::DumpParameters()
{
}

void WaterChickenStrategy::Do(uint32_t idx)
{
    const auto& closedPrice = BaseStrategy::param.subjects[0]->closedPrices[idx];
    const auto& startPrice = BaseStrategy::param.subjects[0]->startPrices[idx];
    const auto& highPrice = BaseStrategy::param.subjects[0]->highPrices[idx];

    BaseStrategy::stat.cash = 0.0;

    double epsilon = 0.01;
    double bigDrawdrawn = 0.04;
    if (!bStartWaterChicken)
    {
        if (highPrice / startPrice > 1.1 - epsilon && closedPrice / startPrice < 1.1 -  2*epsilon)
        {
            bStartWaterChicken = true;
            BaseStrategy::stat.cash = 100.0;
        }
    }
    else
    {
        BaseStrategy::stat.cash = 0.0;
        const auto& lastClosedPrice = BaseStrategy::param.subjects[0]->closedPrices[idx - 1];
        if ((closedPrice - startPrice) / startPrice < (-bigDrawdrawn))
        {
            BaseStrategy::stat.cash = 200.0;
        }
        
        if (closedPrice - startPrice > 0.0)
        {
            BaseStrategy::stat.cash = 300.0;
            bStartWaterChicken = false;
        }
    }

}

MAStrategy::MAStrategy(BaseStrategy::Param in_baseParam, MAStrategy::Param in_param) : BaseStrategy(in_baseParam), param(in_param) {}

void MAStrategy::Reset()
{
    BaseStrategy::Reset();

    maxDrawdown = std::numeric_limits<double>::min();
}

void MAStrategy::DumpParameters()
{
    std::cout << "everyDayCash " << param.everyDayCash << std::endl;
}

void MAStrategy::Do(uint32_t idx)
{
    BaseStrategy::stat.cash += param.everyDayCash;
    BaseStrategy::stat.principle += param.everyDayCash;
    const auto& prices = BaseStrategy::param.subjects[0]->closedPrices;

    auto& subjectRef = BaseStrategy::param.subjects[0];
    if (idx > 0)
    {
        bool bGoldHit = subjectRef->MA5[idx - 1] < subjectRef->MA20[idx - 1] && subjectRef->MA5[idx] > subjectRef->MA20[idx];
        bool bDeadHit = subjectRef->MA5[idx - 1] > subjectRef->MA20[idx - 1] && subjectRef->MA5[idx] < subjectRef->MA20[idx];

        if (bGoldHit)
        {
            BuyInPrice(BaseStrategy::stat.cash, prices[idx], 0);
        }
        else
        {
            SaleInCount(BaseStrategy::stat.holdCount[0], prices[idx], 0);
        }
    }
}
